﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Shop.Core;
using Shop.Core.Data;
using Shop.Core.Domain.Catalog;
using MongoDB.Driver.Builders;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.IdGenerators;

namespace Shop.Data.Mongo
{
    public abstract class MongoRepository<T> //: IRepository<T, string>
        where T : BaseEntity<string>
    {
        #region fields
        protected abstract string CollectionName
        {
            get;
        }

        protected MongoDatabase Database
        {
            set;
            get;
        }

        /// <summary>
        /// Gets the Mongo collection (to perform advanced operations).
        /// </summary>
        /// <remarks>
        /// One can argue that exposing this property (and with that, access to it's Database property for instance
        /// (which is a "parent")) is not the responsibility of this class. Use of this property is highly discouraged;
        /// for most purposes you can use the MongoRepositoryManager<T>
        /// </remarks>
        /// <value>The Mongo collection (to perform advanced operations).</value>
        protected MongoCollection<T> Collection
        {
            get;
            set;
        }

        #endregion

        #region construction
        /// <summary>
        /// Initializes a new instance of the MongoRepository class.
        /// Uses the Default App/Web.Config connectionstrings to fetch the connectionString and Database name.
        /// </summary>
        /// <remarks>Default constructor defaults to "MongoServerSettings" key for connectionstring.</remarks>
        public MongoRepository()
            : this(Util.GetDefaultConnectionString())
        {
        }

        /// <summary>
        /// Initializes a new instance of the MongoRepository class.
        /// </summary>
        /// <param name="connectionString">Connectionstring to use for connecting to MongoDB.</param>
        public MongoRepository(string connectionString)
        {
            this.Database = Util.GetDatabaseFromUrl(connectionString);
            this.Collection = this.Database.GetCollection<T>(this.CollectionName);
        }
        #endregion

        /// <summary>
        /// Returns All the records of T.
        /// </summary>
        /// <returns>IQueryable of T.</returns>
        public IQueryable<T> All()
        {
            return this.Collection.AsQueryable<T>();
        }

        /// <summary>
        /// Adds the new entities in the repository.
        /// </summary>
        /// <param name="entities">The entities of type T.</param>
        public virtual void Insert(IEnumerable<T> entities)
        {
            this.Collection.InsertBatch<T>(entities);
        }

        /// <summary>
        /// Upserts the entities.
        /// </summary>
        /// <param name="entities">The entities to update.</param>
        public void Update(IEnumerable<T> entities)
        {
            foreach (T entity in entities)
            {
                this.Collection.Save<T>(entity);
            }
        }

        /// <summary>
        /// Deletes the entities matching the criteria.
        /// </summary>
        /// <param name="criteria">The expression.</param>
        public void Delete(Expression<Func<T, bool>> criteria)
        {
            foreach (T entity in this.Collection.AsQueryable<T>().Where(criteria))
            {
                this.Delete(entity);
            }
        }

        #region IRepository's fields
        /// <summary>
        /// Returns the T by its given id.
        /// </summary>
        /// <param name="id">The string representing the ObjectId of the entity to retrieve.</param>
        /// <returns>The Entity T.</returns>
        public T GetById(string id)
        {
            return this.Collection.FindOneByIdAs<T>(id);
        }

        public virtual bool Insert(T entity)
        {
            entity.Id = entity.Id ?? MongoDB.Bson.ObjectId.GenerateNewId().ToString();
            WriteConcernResult result = this.Collection.Insert<T>(entity);
            return result == null ? true : result.HasLastErrorMessage;
        }

        public bool Update(T entity)
        {
            WriteConcernResult result = this.Collection.Save(entity);
            return result == null ? true : result.HasLastErrorMessage;
        }

        public bool Delete(T entity)
        {
            WriteConcernResult result = this.Collection.Remove(Query.EQ("_id", entity.Id), RemoveFlags.Single);
            return result == null ? true : result.HasLastErrorMessage;
        }

        public IList<T> FindByIdList(IList<string> idList)
        {
            throw new NotImplementedException();
        }
        #endregion
    }
}
